module TupleExtensions3

module TupleOptimizedOperations =
    type Single<'a> = Single of 'a


    type Split = Split with
        static member ($) (_:Split, Single x1       ) = (x1, ())
        static member ($) (_:Split, (x1,x2)         ) = (x1, Single x2)
        static member ($) (_:Split, (x1,x2,x3)      ) = (x1, (x2,x3))
        static member ($) (_:Split, (x1,x2,x3,x4)   ) = (x1, (x2,x3,x4))
        static member ($) (_:Split, (x1,x2,x3,x4,x5)) = (x1, (x2,x3,x4,x5))
 
    type Join = Join with
        static member ($) (_:Join, ()           ) = fun x -> Single x
        static member ($) (_:Join, Single x1    ) = fun x -> (x,x1)
        static member ($) (_:Join, (x1,x2)      ) = fun x -> (x,x1,x2)
        static member ($) (_:Join, (x1,x2,x3)   ) = fun x -> (x,x1,x2,x3)
        static member ($) (_:Join, (x1,x2,x3,x4)) = fun x -> (x,x1,x2,x3,x4)
 
    let inline (|Cons|) tuple   = Split $ tuple
    let inline Cons (head,tail) = Join $ tail <| head


    type Last = Last with
        static member ($) (_:Last,(_,x))    = x
        static member ($) (_:Last,(_,_,x))  = x
        static member ($) (_:Last,(_,_,_,x))= x


    type Rev = Rev with
        //static member ($) (_:Rev,())         = ()
        static member ($) (_:Rev,Single(x1))    = Single(x1)
        static member ($) (_:Rev,(x1,x2))       = (x2,x1)
        static member ($) (_:Rev,(x1,x2,x3))    = (x3,x2,x1)
        static member ($) (_:Rev,(x1,x2,x3,x4)) = (x4,x3,x2,x1)

    type Map = Map with
        static member ($) (_:Map,())            = fun f -> ()
        static member ($) (_:Map,Single(x1))    = fun f -> Single(f x1)
        static member ($) (_:Map,(x1,x2))       = fun f -> (f x1 ,f x2)
        static member ($) (_:Map,(x1,x2,x3))    = fun f -> (f x1 ,f x2,f x3)
        static member ($) (_:Map,(x1,x2,x3,x4)) = fun f -> (f x1 ,f x2,f x3,f x4)

    type MapType = MapType with
        static member inline ( ?<- ) ((),_:MapType,f)         = ()
        static member inline ( ?<- ) (Single(x1),_:MapType,f)    = Single(f $ x1)
        static member inline ( ?<- ) ((x1,x2),_:MapType,f)       = (f $ x1 ,f $ x2)
        static member inline ( ?<- ) ((x1,x2,x3),_:MapType,f)    = (f $ x1 ,f $ x2,f $ x3)
        static member inline ( ?<- ) ((x1,x2,x3,x4),_:MapType,f) = (f $ x1 ,f $ x2,f $ x3,f $ x4)


    type Ap = Ap with
        static member ($) (_:Ap,(f1,f2))       = fun (x1,x2)       -> (f1 x1,f2 x2)
        static member ($) (_:Ap,(f1,f2,f3))    = fun (x1,x2,x3)    -> (f1 x1,f2 x2,f3 x3)
        static member ($) (_:Ap,(f1,f2,f3,f4)) = fun (x1,x2,x3,x4) -> (f1 x1,f2 x2,f3 x3,f4 x4)
                           
    type ApplySingle = ApplySingle with
        static member ($) (_:ApplySingle,(f1,f2))       = fun x -> (f1 x,f2 x)
        static member ($) (_:ApplySingle,(f1,f2,f3))    = fun x -> (f1 x,f2 x,f3 x)
        static member ($) (_:ApplySingle,(f1,f2,f3,f4)) = fun x -> (f1 x,f2 x,f3 x,f4 x)
                           
    type Fold = Fold with  
        static member ($) (_:Fold,(x1,x2))       = fun (f,s) ->        f (f s x1) x2
        static member ($) (_:Fold,(x1,x2,x3))    = fun (f,s) ->    f ( f (f s x1) x2) x3
        static member ($) (_:Fold,(x1,x2,x3,x4)) = fun (f,s) -> f (f ( f (f s x1) x2) x3) x4
                           
    type Foldl1 = Foldl1 with
        static member ($) (_:Fold,(x1,x2))       = fun f ->       f x1 x2
        static member ($) (_:Fold,(x1,x2,x3))    = fun f ->    f (f x1 x2) x3
        static member ($) (_:Fold,(x1,x2,x3,x4)) = fun f -> f (f (f x1 x2) x3) x4
                           
    type Curry = Curry with
        static member ($) (_:Curry,f:_*_->_)     = fun x1 x2       -> f (x1,x2)
        static member ($) (_:Curry,f:_*_*_->_)   = fun x1 x2 x3    -> f (x1,x2,x3)
        static member ($) (_:Curry,f:_*_*_*_->_) = fun x1 x2 x3 x4 -> f (x1,x2,x3,x4)
                           
    type ToSeq = ToSeq with     
        static member ($) (_:ToSeq,())            = seq []
        static member ($) (_:ToSeq,Single(x1))    = seq [x1]
        static member ($) (_:ToSeq,(x1,x2))       = seq [x1;x2]
        static member ($) (_:ToSeq,(x1,x2,x3))    = seq [x1;x2;x3]
        static member ($) (_:ToSeq,(x1,x2,x3,x4)) = seq [x1;x2;x3;x4]

    type Append = Append with
        static member ( ?<- ) ((x1,x2)      ,_:Append,(y1,y2))       = (x1,x2,y1,y2)
        static member ( ?<- ) ((x1,x2,x3)   ,_:Append,(y1,y2))    = (x1,x2,x3,y1,y2)
        static member ( ?<- ) ((x1,x2,x3,x4),_:Append,(y1,y2)) = (x1,x2,x3,x4,y1,y2)
        static member ( ?<- ) ((x1,x2)      ,_:Append,(y1,y2,y3))       = (x1,x2,y1,y2,y3)    
        static member ( ?<- ) ((x1,x2,x3)   ,_:Append,(y1,y2,y3))    = (x1,x2,x3,y1,y2,y3)
        static member ( ?<- ) ((x1,x2,x3,x4),_:Append,(y1,y2,y3)) = (x1,x2,x3,x4,y1,y2,y3)
        static member ( ?<- ) ((x1,x2)      ,_:Append,(y1,y2,y3,y4))       = (x1,x2,y1,y2,y3,y4)
        static member ( ?<- ) ((x1,x2,x3)   ,_:Append,(y1,y2,y3,y4))    = (x1,x2,x3,y1,y2,y3,y4)
        static member ( ?<- ) ((x1,x2,x3,x4),_:Append,(y1,y2,y3,y4)) = (x1,x2,x3,x4,y1,y2,y3,y4)

    type Transpose = Transpose with
        static member ($) (_:Transpose,((x11,x21),(x12,x22)                     )) = (x11,x12)        ,(x21,x22)
        static member ($) (_:Transpose,((x11,x21),(x12,x22),(x13,x23)           )) = (x11,x12,x13)    ,(x21,x22,x23)
        static member ($) (_:Transpose,((x11,x21),(x12,x22),(x13,x23),(x14,x24) )) = (x11,x12,x13,x14),(x21,x22,x23,x24)
        static member ($) (_:Transpose,((x11,x21,x31)       ,(x12,x22,x32)                              )) = (x11,x12)        ,(x21,x22)        ,(x31,x32)
        static member ($) (_:Transpose,((x11,x21,x31)       ,(x12,x22,x32) ,(x13,x23,x33)               )) = (x11,x12,x13)    ,(x21,x22,x23)    ,(x31,x32,x33)
        static member ($) (_:Transpose,((x11,x21,x31)       ,(x12,x22,x32) ,(x13,x23,x33),(x14,x24,x34) )) = (x11,x12,x13,x14),(x21,x22,x23,x24),(x31,x32,x33,x34)
        static member ($) (_:Transpose,((x11,x21,x31,x41),(x12,x22,x32,x42)                                     )) = (x11,x12)        ,(x21,x22)        ,(x31,x32)        ,(x41,x42)
        static member ($) (_:Transpose,((x11,x21,x31,x41),(x12,x22,x32,x42),(x13,x23,x33,x43)                   )) = (x11,x12,x13)    ,(x21,x22,x23)    ,(x31,x32,x33)    ,(x41,x42,x43)
        static member ($) (_:Transpose,((x11,x21,x31,x41),(x12,x22,x32,x42),(x13,x23,x33,x43),(x14,x24,x34,x44) )) = (x11,x12,x13,x14),(x21,x22,x23,x24),(x31,x32,x33,x34),(x41,x42,x43,x44)
        
module Tuple =

    open TupleOptimizedOperations

    let inline split x = Split $ x
    let inline head x = split x |> fst
    let inline tail x = split x |> snd
    let inline last x = Last $ x
    let inline cons h t = (Join $ t) h
    let inline rev x = Rev $ x
    let inline map f x = (Map $ x) f    
    let inline mapType f x = x ? (MapType) <- f
    let inline (<*>) x y = Ap $ x <| y
    let inline applySingle x = ApplySingle $ x
    let inline fold f y0 x = (Fold $  x) (f,y0)
    let inline foldl1 f x = (Foldl1 $  x) f
    let inline curry x = Curry $  x
    let inline toSeq x = ToSeq $  x
    let inline append (x,y) = x ? (Append) <- y
    let inline transpose x = Transpose $  x

    let inline toList x = x |> toSeq |> Seq.toList